import html2Canvas from 'html2canvas'
import jsPDF from 'jspdf'
import { chunk } from 'lodash-unified'

/**
 * 打印码说明
 * * 第一位为打印方向, 即第一打印方向
 * * 第二位也为打印方向, 但可省略, 不传时, 仅按一个方向打印
 */
const exportMode = {
  ['h-v']: 'h-v', //  打印顺序: 先横向, 再垂直
  ['h']: 'h', //  仅横向
  ['v']: 'v', //  仅垂直 [默认]
  ['v-h']: 'v-h' //  先垂直, 再横向
}

/**
 * A4 纸张比例
 */
const A4Size = {
  portrait: [595.28, 841.89], // 纵向比
  landscape: [841.89, 595.28] // 横线比
}

/**
 * 打印配置参数
 */
const configTemp = {
  fileName: '文件.pdf',
  orientation: 'portrait',
  multiMode: exportMode.v,
  scale: 1,
  chunkSize: 10
} as ConfigTemp

interface ConfigTemp {
  /**
   * 导出文件名
   * @default '文件.pdf'
   */
  fileName?: string
  /**
   * 打印方向
   * @description landscape: 横向, portrait: 纵向;
   * @default 'portrait'
   */
  orientation?: 'portrait' | 'p' | 'l' | 'landscape'
  /**
   * 多页打印控制
   * @default 'v'
   */
  multiMode?: keyof typeof exportMode
  /**
   * 放大倍数
   * @description 该参数谨慎使用, 容易导致内存占用过高, 拖慢速度, 甚至导致失败
   * @default 1
   */
  scale?: number
  /**
   * 切分大小
   * @description 当调用 pdfExportMulti 时生效
   * @default 10
   */
  chunkSize?: number
}

interface DocConfig {
  contentSize: {
    w: number
    h: number
  }
  unExportSize: {
    w: number
    h: number
  }
  pageSize: {
    w: number
    h: number
  }
  docImage: string
}

/**
 * 校验配置
 */
function validateConfig(config: ConfigTemp) {
  if (!config.fileName) {
    return 'fileName'
  }
  if (!config.chunkSize) {
    return 'chunkSize'
  }
  if (!config.multiMode) {
    return 'multiMode'
  }
  if (!config.orientation) {
    return 'orientation'
  }
  if (!config.scale) {
    return 'scale'
  }
}

function exportUnion(
  pdf: jsPDF,
  mode: string,
  docConfig: DocConfig,
  A4Width: number,
  A4Height: number
) {
  const exportOffset = { x: 0, y: 0 }
  const { docImage, contentSize, unExportSize, pageSize } = docConfig

  if (mode === exportMode.v) {
    /**
     * * imgW, imgH 与 contentW, contentH 的关系如下
     * * imgW / contentW = imgH / contentH
     * * 在该关系中, contentW, contentH 是已知的, 但是, imgW, imgH 是需要根据打印列数（行数）给出的
     *
     * * 例子一: 在纸张纵向, 打印列数为 1 时, imgW = a4纸宽*1, 每次垂直偏移量为减少 page.h / 1
     * * 此时, img 与 content 的关系, 可推导出 imgH = imgW / contentW * contentH
     * * 所以, 初始值, 还与 colCount（列数） 有关
     */
    const colCount = 1 // 列数
    const imgW = A4Width * colCount
    const imgH = (imgW / contentSize.w) * contentSize.h

    console.log('contentSize: ', contentSize)
    console.log('page: ', pageSize)
    console.log('imgW, imgH, a4W, a4H: ', imgW, imgH, A4Width, A4Height)
    // 未导出区域还有高时
    while (Math.floor(unExportSize.h) > 0) {
      pdf.addImage(docImage, 'JPEG', exportOffset.x, exportOffset.y, imgW, imgH)

      console.log('unExport.h: ', unExportSize.h)
      console.log('exportOffset: ', exportOffset)
      unExportSize.h = unExportSize.h - pageSize.h / colCount // 处理 纵向未导出区域
      exportOffset.y = exportOffset.y - A4Height // 处理 纵向偏移
      if (Math.floor(unExportSize.h) > 0) {
        pdf.addPage()
      }
    }
    console.log('unExport.h: ', unExportSize.h)
  }
  if (mode === exportMode.h) {
    /**
     * * imgW, imgH 与 contentW, contentH 的关系如下
     * * imgW / contentW = imgH / contentH
     * * 在该关系中, contentW, contentH 是已知的, 但是, imgW, imgH 是需要根据打印列数（行数）给出的
     *
     * * 例子一: 在纸张纵向, 打印列数为 1 时, imgW = a4纸宽*1, 每次垂直偏移量为减少 page.h / 1
     * * 此时, img 与 content 的关系, 可推导出 imgH = imgW / contentW * contentH
     * * 所以, 初始值, 还与 colCount（列数） 有关
     */
    const colCount = 1 // 列数
    const imgW = A4Width * colCount
    const imgH = (imgW / contentSize.w) * contentSize.h

    console.log('contentSize: ', contentSize)
    console.log('page: ', pageSize)
    console.log('imgW, imgH, a4W, a4H: ', imgW, imgH, A4Width, A4Height)
    // 未导出区域还有高时
    while (Math.floor(unExportSize.h) > 0) {
      pdf.addImage(docImage, 'JPEG', exportOffset.x, exportOffset.y, imgW, imgH)
      console.log('unExport.h: ', unExportSize.h)
      unExportSize.h = unExportSize.h - pageSize.h / colCount // 处理 纵向未导出区域
      exportOffset.y = exportOffset.y - A4Height // 处理 纵向偏移
      if (Math.floor(unExportSize.h) > 0) {
        pdf.addPage()
      }
    }
    console.log('unExport.h: ', unExportSize.h)
  }
  if (mode === exportMode['h-v']) {
    /**
     * * imgW / contentW = imgH / contentH
     * * 例子二: 纸张纵向, 打印列数为 2 时, 打印行数为 4 时,
     */
    const colCount = 2 // 列数
    const rowCount = 3 // 行数
    const imgW = A4Width * colCount
    const imgH = (imgW / contentSize.w) * contentSize.h

    console.log('imgW, imgH, a4W, a4H: ', imgW, imgH, A4Width, A4Height)
    console.log('page: ', pageSize)
    for (let i = 0; i < rowCount; i++) {
      for (let j = 0; j < colCount; j++) {
        pdf.addImage(
          docImage,
          'JPEG',
          pageSize.w / colCount,
          pageSize.h / rowCount,
          A4Width,
          imgH
        )
        pdf.addPage()
      }
    }

    console.log('unExport.h: ', unExportSize.h)
  }
}

/**
 * PDF导出 - 单节点
 * @param dom DOM 节点
 * @param config 导出配置
 * @param callback 回调方法
 */
export async function pdfExportSingle(
  dom: HTMLElement,
  config = configTemp,
  callback = (...args: any[]) => {}
) {
  config = { ...configTemp, ...config }

  const err = validateConfig(config)

  if (err) {
    console.error(`PDF导出失败：${err} 值不正确`, config)
    return
  }

  // @ts-ignore
  const [A4Width, A4Height] = A4Size[config.orientation]
  const pdf = new jsPDF({
    orientation: config.orientation,
    unit: 'pt',
    format: 'a4'
  })

  const canvas = await html2Canvas(dom, {
    scale: config.scale,
    allowTaint: true
  })

  const rect = dom.getBoundingClientRect()
  const contentSize = {
    w: rect.width * config.scale!,
    h: rect.height * config.scale!
  }

  const docImage = canvas.toDataURL('image/jpeg', 1.0)

  const docConfig: DocConfig = {
    contentSize,
    unExportSize: {
      w: contentSize.w,
      h: contentSize.h
    },
    pageSize: {
      w: (contentSize.h / A4Height) * A4Width,
      h: (contentSize.w / A4Width) * A4Height
    },
    docImage
  }

  switch (config.multiMode) {
    case exportMode.v:
      exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
      break
    case exportMode.h:
      exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
      break
    case exportMode['v-h']:
      exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
      break
    case exportMode['h-v']:
      exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
      break
    default:
      break
  }

  pdf.save(config.fileName)
  if (typeof callback === 'function') {
    const timer = setTimeout(() => {
      callback()
      timer && clearTimeout(timer)
    }, 300)
  }
}

/**
 * PDF导出 - 多节点
 * @description 适用于大文件导出
 * @param doms DOM 节点数组
 * @param config 导出配置
 * @param callback 回调方法
 */
export async function pdfExportMulti(
  doms: HTMLElement[],
  config = configTemp,
  callback = (...args: any[]) => {}
) {
  config = { ...configTemp, ...config }

  const err = validateConfig(config)

  if (err) {
    console.error(`PDF导出失败：${err} 值不正确`, config)
    return
  }

  // @ts-ignore
  const [A4Width, A4Height] = A4Size[config.orientation]
  const pdf = new jsPDF({
    orientation: config.orientation,
    unit: 'pt',
    format: 'a4'
  })

  // 将节点数组按切分大小等分，减少canvas生成的次数
  const domArr = chunk(doms, config.chunkSize)
  for (const [index, dom] of domArr.entries()) {
    const domWrapper = document.createElement('div')
    domWrapper.style.position = 'absolute'
    domWrapper.style.left = '-9999px'
    dom.forEach(v => {
      // 必须深拷贝，否则会影响原有节点
      domWrapper.append(v.cloneNode(true))
    })
    document.body.append(domWrapper)

    const canvas = await html2Canvas(domWrapper, {
      scale: config.scale,
      allowTaint: true
    })

    const rect = domWrapper.getBoundingClientRect()
    const contentSize = {
      w: rect.width * config.scale!,
      h: rect.height * config.scale!
    }

    const docImage = canvas.toDataURL('image/jpeg', 1.0)

    const docConfig: DocConfig = {
      contentSize,
      unExportSize: {
        w: contentSize.w,
        h: contentSize.h
      },
      pageSize: {
        w: (contentSize.h / A4Height) * A4Width,
        h: (contentSize.w / A4Width) * A4Height
      },
      docImage
    }

    switch (config.multiMode) {
      case exportMode.v:
        exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
        break
      case exportMode.h:
        exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
        break
      case exportMode['v-h']:
        exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
        break
      case exportMode['h-v']:
        exportUnion(pdf, config.multiMode, docConfig, A4Width, A4Height)
        break
      default:
        break
    }

    // 最后一页不添加空白页
    if (index !== domArr.length - 1) {
      pdf.addPage()
    }

    // 删除临时元素
    document.body.removeChild(domWrapper)
  }

  pdf.save(config.fileName)

  if (typeof callback === 'function') {
    const timer = setTimeout(() => {
      callback()
      timer && clearTimeout(timer)
    }, 300)
  }
}
